package sim;

import game.Constants;
import game.player.Agent;
import habitat.Habitat;
import habitat.HabitatCreator;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import util.ResultFileWriter;

public class Simulation {
	// / agents are selected to be moved to another container with probability
	// / based on success
	
	private Logger logger = Logger.getLogger(Simulation.class.getName());
	
	private static final int AGENT_MOBILITY_MODE_PROBABILISTIC = 0;

	// / agents are selected based on
	private static final int AGENT_MOBILITY_MODE_SUCCESS_RATIO = 1;

	private static final int MARKET_TRANSITION_PROB_EQUAL = 0;

	private static final int MARKET_TRANSITION_PROB_EDGE_WEIGTH = 1;

	private static final int MOBILE = 1;
	
	Map<Integer, Integer[]> neighHashtable;
	Map<Integer, Double> habitatDSFs;
	
	public static String logFileName ;

	private static int ID = 0;

	public static int getID() {
		return ID++;
	}

	public static ResultFileWriter output;
	
	public static ResultFileWriter loggerHabInfo;

	private static Simulation singleton = new Simulation();

	public Simulation() {
		rand = new Random();
	}

	public static Simulation getSimulation() {
		return singleton;
	}

	private Random rand;

	public void nextStep() {

	}

	private static int currentCycle;

	public static int getCycle() {
		return currentCycle++;
	}

	public void startSimulation() {
		// to measure time interval among processes
		Long time[] = new Long[5];
		Map<Integer, Habitat> habitats = HabitatCreator.getHabitatFactory()
				.getHabitats();
		neighHashtable = HabitatCreator.getHabitatFactory().getNeighHashtable();
		habitatDSFs = HabitatCreator.getHabitatFactory().getHabitatDFs();
		
		// list of agents moved from one habitat to another 
		// one map for (agent,habitat)pair, habitat where agent leaves
		// the other map for (agent,habitat)pair, habitat where agent moves to
		Map<Agent, Integer> removeAgentFromHab = new HashMap<Agent, Integer>();
		Map<Agent, Integer> addAgentToHab = new HashMap<Agent, Integer>();
	
	   output.open();

		time[0] = System.currentTimeMillis();
		for (int k = 0; k <= Constants.NUM_OF_ITERATION; k++) {
			double Fc = 0.0;
			double d ;
			int total_agent_number = 0;
			int total_defect_number = 0;
			int total_cooperate_number = 0;
			//=================================================================//
			// Logging  
			//=================================================================//
			if(k == Constants.NUM_OF_ITERATION)
				output.logWoReOpen("\n");
			for (Iterator<Habitat> iterr = habitats.values().iterator(); iterr
			.hasNext();) {
					Habitat habitat = iterr.next();
					d = habitat.getDivertsityValue();
				 	total_agent_number += habitat.getNumberOfAgents();
				 	total_defect_number += habitat.getNumOfDefects();
				 	total_cooperate_number += habitat.getNumOfCooperates();
				 	if(k == Constants.NUM_OF_ITERATION){
				 		output.logWoReOpen(habitat.getID()+"\t"+(float)d+"\t"+habitat.getNumOfCooperates()+"\t"+habitat.getNumOfDefects()+"\n");
				 	}
			}
			Fc =(double)(total_cooperate_number)/(double)total_agent_number;
			logger.info("\n Number of Defects: "+total_defect_number+ "\t Number of Cooperates : "+total_cooperate_number);
			logger.info(" Frequency of Cooperates : "+Fc);
		 	if(k != Constants.NUM_OF_ITERATION)
		 		output.logWoReOpen(k+"\t"+Fc+"\t"+total_cooperate_number+"\t"+total_defect_number+"\t"+ removeAgentFromHab.size()+"\n");

		 	//=================================================================//
		 	
			logger.info("\nIteration : " + k);
			time[1] = System.currentTimeMillis();
			for (Iterator<Habitat> iterr = habitats.values().iterator(); iterr
					.hasNext();) {
				 iterr.next().startTournament();
			}
			time[2] = System.currentTimeMillis();
			logger.info("\nEncounters time  for iteration" + k + " is "		+ (time[2] - time[1]));
		
			
			removeAgentFromHab.clear();
			addAgentToHab.clear();
			
			
			for (Iterator<Habitat> iterr2 = habitats.values().iterator(); iterr2
					.hasNext();) {

				Habitat habitat = iterr2.next();
				if(logger.isLoggable(Level.FINER)){
					if(k==1|| k==100){
					 logger.finer("Total score  in the container  "
					 + habitat.getID() +" is "+habitat.getTotalScore()+ " with diversity: "+habitat.getDivertsityValue()+" after tournament " + k + "\n");
					 habitat.printScoreTable();
					}
				}
			
				Vector<Agent> players = habitat.getPlayers();
				double probablity;
	
				for (Iterator<Agent> iterator = players.iterator(); iterator.hasNext();) {
				
					Agent player = iterator.next();
					
					// change moved agent strategy with one neighbors' strategies
					habitat.changeAgentStratgey(player);

					if(Constants.MOBILITY == MOBILE){
						if(player.getScore() == 0.0)
							probablity = 1.0;
						else if(player.getStrategy() == game.strategy.Strategy.DEFECT)
							probablity = 1.0 - player.getScore() / ( habitat.reScalePayOff( (float) Constants.BVALUE)*((double)player.outdegree) );
						else
							probablity = 1.0 - player.getScore() / ( habitat.reScalePayOff( (float) 1.0)*((double)player.outdegree) );
						
					
						if (rand.nextDouble() < probablity){
							//select new habitat to be move
							int selhabitatid = selectNewHabitat(habitat);
							
							if (selhabitatid < 0)	continue;
							 //Habitat newHabitat = habitats.get(selhabitatid);
							if(logger.isLoggable(Level.FINE)){
								 logger.fine(" Agent is moved to the habitat "
								 + selhabitatid + " with diversity "+habitatDSFs.get(selhabitatid));
							}
							// add agent to table to remove from old habitat
							removeAgentFromHab.put(player, habitat.getID());

							addAgentToHab.put(player, selhabitatid);
						}
					}
				}
				
			}
			//transfer agents between containers
			for (Iterator<Agent> iterator = removeAgentFromHab.keySet().iterator(); iterator.hasNext();) {
				Agent agent = iterator.next();
				
				Habitat habitat = habitats.get(removeAgentFromHab.get(agent));
				Habitat newhabitat = habitats.get(addAgentToHab.get(agent));
				// remove the agent from old habitat
				removeAgentFromHabitat(agent, habitat);
				// adding agent into new habitat
				addAgentIntoHabitat(agent, newhabitat,Constants.NUM_OF_CONNS);
				if(logger.isLoggable(Level.FINER))
					logger.fine("After inserting agent"+agent.getID()+" into habitat "+ newhabitat.getID());
					logger.fine("Agents number: "+newhabitat. getNumberOfAgents()+" connectivty :"+newhabitat.getIntmatSize());
					logger.fine("After removing agent"+agent.getID() +" from habitat "+ habitat.getID());
					logger.fine("Agents number: "+habitat.getNumberOfAgents()+" connectivty :"+habitat.getIntmatSize());
			}
		
			time[3] = System.currentTimeMillis();
			logger.info("Movement time  for iteration " + k + " is "		+ (time[3] - time[2]));
			logger.info("Total moved agents  " + removeAgentFromHab.size() );


		}
		time[4] = System.currentTimeMillis();
		Long duration = time[4]-time[0];
		logger.info("Total running time is "+duration/(60000*60)+":"+(duration % (60000*60))/60000+":"+(duration % 60000) / 1000);
		//logger.close();
		//loggerHabInfo.close();
		output.close();
	}



	/*
	 * select market transition (or container) where selected agents will be
	 * moved to.
	 */
	private int selectNewHabitat(Habitat habitat) {
		Integer[] neighbors = neighHashtable.get(habitat.getID());
		// /Equal probability among alternative transitions (for unweighed
		// case)
		int selhab = 0;
		if (Constants.MARKET_TRANSITION_PROB == MARKET_TRANSITION_PROB_EQUAL) {
			selhab = rand.nextInt(neighbors.length);
			return neighbors[selhab];  
		}
		// /With probability based on weights of the edges
		else if (Constants.MARKET_TRANSITION_PROB == MARKET_TRANSITION_PROB_EDGE_WEIGTH) {
			double prob ;
			
				selhab = rand.nextInt(neighbors.length);
				prob = 1.0 / (  1.0 + Math.exp( (habitat.getDivertsityValue() - habitatDSFs.get(neighbors[selhab]))/ Constants.K2VALUE)  );
				double nextDouble =  rand.nextDouble();
				if (prob > nextDouble)
					return neighbors[selhab];
			//System.err.println("No neighbor is selected to tarnsfer the leaving agents into a new habitat");
			return -1;

		} else {
			System.err.println("Market transition rule undefined");
			return -1;
		}
	}

	private boolean removeAgentFromHabitat(Agent removedAgent, Habitat habitat) {
		//Vector<Agent> agents = habitat.getPlayers();
		//Vector<int[]> intmat = habitat.getIntmat();

		Vector<Agent> connectedAgents = habitat
				.getConnectedAgents(removedAgent);

		int numConnectedAgents = connectedAgents.size();
		if( numConnectedAgents <= 1){
			habitat.removeAgent(removedAgent);
			//System.out.println("After removing agent " + removedAgent.ID+" from habitat "+habitat.getID());
			//changeAgentStratgey(removedAgent, connectedAgents);
			return true;
		}
			
		double prob[] = new double[numConnectedAgents];
		int totalDegree = 0;
		for (Iterator<Agent> iterator = connectedAgents.iterator(); iterator
				.hasNext();) {
			Agent agent = iterator.next();
			totalDegree += agent.outdegree;
		}
		prob[0] = (double) connectedAgents.elementAt(0).outdegree
				/ (double) totalDegree;
		for (int i = 1; i < numConnectedAgents; i++) {
			prob[i] = prob[i - 1]
					+ (double) connectedAgents.elementAt(i).outdegree
					/ (double) totalDegree;
		}
		int selattach = -1;
		selattach = selectProb(numConnectedAgents, prob, rand);
		
		if (selattach < 0 ) {
			habitat.removeAgent(removedAgent);
			//System.out.println("After removing agent " + removedAgent.ID+" from habitat "+habitat.getID());
			//changeAgentStratgey(removedAgent, connectedAgents);
			return true;
		}
		
		// remove agent and rewires its connections with a selected connected agent.
		Agent selAgent = connectedAgents.elementAt(selattach);

		// replace the connections of removed agent on the selected agent
		// remove the connection between selected agent end removed agent
		Vector<int[]> addedEdges = new Vector<int[]>();
		int[][] conectedEdgesToRemoved = habitat.getConnectedEdges(removedAgent).toArray(new int[0][]);
		int[][] connectedEdgesToSelected  = habitat.getConnectedEdges(selAgent).toArray(new int[0][]);
		for (int i=0;i<numConnectedAgents;i++) {
			int[] edge = conectedEdgesToRemoved[i];
			if (removedAgent.getID() == edge[1]) {
				if (selAgent.getID() != edge[0]) {
					int[] newedge = new int[2];
					newedge[0] = edge[0];
					newedge[1] = selAgent.getID();
					addedEdges.add(newedge);

				}
			} else if (removedAgent.getID() == edge[0]) {
				if (selAgent.getID() != edge[1]) {
					int[] newedge = new int[2];
					newedge[0] = selAgent.getID();
					newedge[1] = edge[1];
					addedEdges.add(newedge);
				}
			}

		}
		habitat.removeAgent(removedAgent);
		
		for (Iterator<int[]> iterator = addedEdges.iterator(); iterator.hasNext();) {
			int[] is =iterator.next();
			boolean dublicate =false;
			for(int j=0;j<connectedEdgesToSelected.length;j++)
			{
				int[] edge =connectedEdgesToSelected[j];
				if((is[0]== edge[0]) && (is[1]== edge[1]))
				{
					dublicate =true;
					break;
				}
				else if((is[0]== edge[1]) && (is[1]== edge[0]))
				{
					dublicate =true;
					break;
				}
			}
			if(!dublicate)
				habitat.addEdge(is);
		}
		if(logger.isLoggable(Level.FINE)){
			logger.fine("After removing agent " + removedAgent.getID()
				+ " replacing its connectivity  with agent " + selAgent.getID()+" in habitat "+habitat.getID());
			if(logger.isLoggable(Level.FINER)){
				logger.finer("Interconnectivity matrix of habitat"+ habitat.getID());
				for (Iterator<int[]> iterator = habitat.getIntmat().iterator(); iterator.hasNext();) {
					int[] is = iterator.next();
					logger.finer(" Edge : " + is[0] + " -- " + is[1]);
				}
			}
		}
		return true;
	}

	public void addAgentIntoHabitat(Agent newAgent, Habitat habitat,
			int numOfCons) {
		Vector<Agent> agents = habitat.getPlayers();
		int numAgents = agents.size();
		if(numAgents == 0){
			// add agent into the habitat
			habitat.addAgent(newAgent);
			return;
		}
		if(numOfCons > numAgents )
			numOfCons = numAgents;
		int totalDegree = habitat.getTotalOutDegree();
		Vector<int[]> edges = new Vector<int[]>();
	
		double prob[] =new double[numAgents];
		
		prob[0] = (double) agents.elementAt(0).outdegree
			/ (double) totalDegree;
		for (int i = 1; i < numAgents; i++) {
			prob[i] = prob[i - 1] + (double) agents.elementAt(i).outdegree/ (double) totalDegree;
		}
		for (int k = 0; k < numOfCons; k++) {
			int acon[] = createRandomEdge(numAgents, agents, newAgent, prob, totalDegree);
			if (isInList(edges, acon))
				k--;
			else {
				if(logger.isLoggable(Level.FINER))
					logger.finer("Conncted edge :" + acon[0] + " --- "+ acon[1]);
				edges.add(acon);
			}
		}
		// add agent into the habitat
		habitat.addAgent(newAgent);
		// add edge the connectivity matrix
		for (Iterator<int[]> iterator = edges.iterator(); iterator.hasNext();) {
			int[] is = iterator.next();
			if(logger.isLoggable(Level.FINER))
				logger.finer("Conncted edge :" + is[0] + " --- "	 + is[1]);
			habitat.addEdge(is);
		}

	}
	


	public int[] createRandomEdge(int numAgents,Vector<Agent> agents, Agent newAgent,double prob[], int totaldegree) {

		Agent attach_point;
		
		int sel_attach_point = selectProb(numAgents, prob, rand);
		
		attach_point = agents.elementAt(sel_attach_point);
		
		return new int[] { newAgent.getID(), attach_point.getID() };

	}

	private boolean isInList(Vector<int[]> list, int edge[]) {

		for (Iterator<int[]> iterator = list.listIterator(); iterator.hasNext();) {
			int[] list_edge = iterator.next();
			if (list_edge[0] == edge[0] && list_edge[1] == edge[1])
				return true;
			else if (list_edge[0] == edge[1] && list_edge[1] == edge[0])
				return true;
		}
		return false;
	}
	// probability values must lie in order 
	public int selectProb(int N, double[] probabilities,  Random rand) {

		double val = rand.nextDouble();

		// / do a binary search for the location i such that
		// / probabilities[i - 1] < val <= probabilities[i]
		int i = 0;
		int left = 0;
		int right = N - 1;
		while (right >= left) {
			int middle = (left + right) / 2;
			if (val - probabilities[middle] < 0.0)
				right = middle - 1;
			else if (val - probabilities[middle] > 0.0)
				left = middle + 1;
			else {
				i = middle;
				break;
			}
		}

		if (right < left)
			i = left;

		// / in the case there were neighboring
		// / items with probability 0
		while (i > 0 && probabilities[i - 1] == val)
			i--;

		return i;	
	}

	

}